17. Exercise: Synchronization
Exercise: Synchronization
Parallel Test Runner
Remember that unit testing framework you designed in the previous lesson? You have a test suite that has been taking too long to finish.
You have an idea to speed it up: What if the individual unit tests could run in parallel, using multiple threads? For example, suppose you have four unit test methods that each take a few seconds to complete. When running these tests normally, the test runner can take 10+ seconds to complete. On the other hand, if you could make the test runner execute the tests in parallel, you'd expect it to take only a few seconds total to complete, since all the test methods run concurrently with one another.
Your starter code for this exercise is the solution to the Custom ClassLoader exercise from the previous lesson. If you want, you can run the tests now — they should take 10+ seconds to complete.
javac *.java */*.java
java -ea TestRunner tests/ CalculatorTest
Note: If you peek in CalculatorTest.java
, you'll notice the tests aren't really doing a whole lot; they use Thread#sleep()
to make them artificially long-running for the purposes of this exercise.
Here's what you need to do to enhance the test runner to run tests in parallel:
- Create an
ExecutorService
usingExecutors.newFixedThreadPool(...)
. This is the thread pool that will run all the tests. - There should already be a loop that decide which test methods to run. You should update the contents of the loop to run inside of a
Runnable
. You don't actually have to create a separate class for this — the easiest way is to use a lambda:
for (...) {
executor.execute(() -> {
// TODO: Run the next test.
});
}
- Next, you need to synchronize access to the
passed
andfailed
lists. There are a variety of ways to do this. You could use thesynchronized
keyword, an explicitReentrantLock
, or even aCopyOnWriteArrayList
if you are feeling adventurous (thoughCopyOnWriteArrayList
is probably overkill here). - Finally, outside the loop, you need to wait for all the test threads to finish (this is also called joining the threads. There are multiple ways to do this. One way is to use a
CountDownLatch
initialized with the number of test methods. Then each test thread should callCountDownLatch#countDown()
when it's done. Then, outside the loop, you can callCountDownLatch#await()
. This method waits until theCountDownLatch
counts down to zero, which means all the threads have called thecountDown()
method. When you are done joining the test threads in this way, make sure you callExecutorService#shutdown()
, or else the program might not actually exit.
Running the solution
Try out the parallel test runner!
javac *.java */*.java
java -ea TestRunner tests/ CalculatorTest
Do the tests complete noticeably faster than before?
TODO List
Task Feedback:
Congratulations, you built a multi-threaded parallel test runner!
In addition to running test suites faster, running tests in parallel can be useful to uncover subtle concurrency bugs in the systems being tested (and sometimes bugs in the test fixtures themselves).
Code
If you need a code on the https://github.com/udacity.
export PATH=/data/jdk-15.0.1/bin:$PATH
export JAVA_HOME=/data/jdk-15.0.1/bin